package plus.exp;
import java.util.*;

import plus.Gripe;
import plus.Identifier;
import plus.S_Record;
import plus.Struct;
import plus.Symbol;
import plus.TS_Record;
import plus.Type;
import plus.TypeSym;
import plus.run.Emit;
public class E_Record extends Exp {
	final Identifier ident;
	final Exp[] fields;
	String[] names;
	public E_Record(Identifier ident, List<Exp> fields) {
		this.ident = ident;
		this.names = new String[fields.size()];
		this.fields = new Exp[names.length];
		Set<String> seen = new HashSet<String>();
		int i = 0;
		for (Exp e:fields) {
			String name = e.impliedName();
			//System.out.println(name);
			if (null == name) throw new Gripe("Anonymous Field near "+ident);
			if (seen.contains(name)) throw new Gripe("Duplicate Field: "+name+" at "+ident);
			names[i] = name;
			this.fields[i] = e;
			i ++;
		}
	}
	@Override
	public
	void threadScope() {
		Symbol<TypeSym> sym = ident.asType();
		if (sym == null) throw new Gripe("No such type at "+ident);
		else if (sym.definition instanceof TS_Record) thread((TS_Record)sym.definition);
		else throw new Gripe("Not a record type at "+ident);
	}
	Struct theStructure;
	private void thread(TS_Record ts) {
		theStructure = ts.struct;
		if (!theStructure.isNames(names)) {
			theStructure.reportExpected();
			System.out.println("--- received ---");
			for (String s:names) System.out.println("field: "+s);
			throw new Gripe("incorrect field names near "+ident);
		}
		
		S_Record sol = new S_Record(ts, Type.fresh_vars(ts.arity()));
		// Unify each field with the solution's type at that field
		// (which only exists after threadScope)
		
		for (int i=0; i<fields.length; i++) {
			fields[i].threadScope();
			Type.unify(fields[i].type, sol.typeOfField(names[i]));
		}
		
		// Configure the type variable for the overall construction.
		type = Type.fresh(sol);
	}
	@Override String impliedName() { return ident.name; }
	@Override String aThis() { return "a literal record"; }
	@Override
	public void compilePush() {
		// Basically the same as an array initializer, but with heterogeneous types,
		// and in the order given by the syntax -- not that order should matter, but it
		// might be observable and a program should be able to rely on order. Importantly,
		// there is no "this" during construction.
		Emit.newArray(fields.length);
		for (int i=0; i<fields.length; i++) {
			fields[i].compilePush();
			Emit.storeIndex(theStructure.indexOfField(names[i]));
		}
		// Leave the reference to the "record" on the top of the stack.
	}
	
}
